﻿using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using RayDen.Library.Components.SystemComponents;
using RayDen.RayEngine.Core.Interface;
using RayDen.RayEngine.Core.Threads;
using RayDen.RayEngine.Core.Types;
using RayDen.RayEngine.Samplers;

namespace RayDen.RayEngine.NextGen
{
    class DeviceRenderThreadBase : RenderThread
    {
        protected IIntersectionEngine intersectionDevice;
        protected PathIntegrator[] _pathRayProcessors;
        protected RayBuffer[] rayBuffers;
        protected SampleBuffer sampleBuffer;
        protected Task renderThread;

        protected int width, height;
        protected new RayEngineScene scene;
        protected new IPrimarySpaceSampler sampler;

        protected DeviceRenderThreadBase(int index, RayEngineScene scene)
            : base(index, scene)
        {
            this.scene = scene;
        }

        protected DeviceRenderThreadBase(int index,
                                       IIntersectionEngine device,
                                       RayEngineScene scn,
                                       bool lowLatency,
                                       ImageFilmBase pixelDevice,
                                       IPrimarySpaceSampler sampler, SurfaceSampler ss)
            : this(index, scn)
        {
            Init(device, lowLatency, pixelDevice, sampler, ss);
        }

        protected void Init(IIntersectionEngine device, bool lowLatency, ImageFilmBase pixelDevice, IPrimarySpaceSampler sampler,
                          SurfaceSampler ss)
        {
            intersectionDevice = device;
            // Sample buffer
            var sampleBufferSize = lowLatency ? (SAMPLE_BUFFER_SIZE / 4) : SAMPLE_BUFFER_SIZE;
            sampleBuffer = new SampleBuffer(sampleBufferSize);

            width = sampler.Width;
            height = sampler.Height;
            var rayBufferSize = lowLatency ? (RayBuffer.RayBufferSize / 8) : RayBuffer.RayBufferSize;
            this.sampler = sampler;
            _pathRayProcessors = new PathIntegrator[DeviceRenderBufferCount];
            rayBuffers = new RayBuffer[DeviceRenderBufferCount];

            for (int i = 0; i < DeviceRenderBufferCount; i++)
            {
                rayBuffers[i] = new RayBuffer(rayBufferSize);
                rayBuffers[i].UserData = threadIndex;

                _pathRayProcessors[i] = this.CreatePathBuffer(scene.MaxPaths, scene, pixelDevice, sampler, ss);
            }

            renderThread = null;
        }

        private CancellationTokenSource cancelToken;


        protected virtual PathIntegrator CreatePathBuffer(int maxPath, RayEngineScene scene, ImageFilmBase pixelDevice,
                                                  IPrimarySpaceSampler sampler, SurfaceSampler ss)
        {
            var integrator = new PathIntegrator();
            integrator.Init(maxPath, scene, pixelDevice, sampler, ss);
            return integrator;
        }

        public override void Start()
        {
            base.Start();

            sampler.Init(width, height);

            sampleBuffer.Reset();
            for (int i = 0; i < DeviceRenderBufferCount; i++)
            {
                rayBuffers[i].Reset();
                rayBuffers[i].ResetUserData();
                rayBuffers[i].UserData = i;
                _pathRayProcessors[i].ReInit();
            }
            cancelToken = new CancellationTokenSource();
            // Create the thread for the rendering
            this.renderThread = Task.Factory.StartNew(() => RenderThreadImpl(cancelToken.Token, this), cancelToken.Token, TaskCreationOptions.LongRunning, TaskScheduler.Current);
            intersectionDevice.Start();
        }
        public override void Interrupt()
        {
            if (this.renderThread.Status == TaskStatus.Running)
            {
                cancelToken.Cancel();
            }
        }
        public override void Stop()
        {
            if (this.renderThread.Status == TaskStatus.Running)
            {
                cancelToken.Cancel();
            }
            this.renderThread.Dispose();
            this.renderThread = null;
            base.Stop();
        }
        public override void ClearPaths()
        {
            for (int i = 0; i < DeviceRenderBufferCount; i++)
            {
                _pathRayProcessors[i].ClearPaths();
            }
        }

        public override int GetPass()
        {
            return sampler.GetPass();
        }

        protected static void RenderThreadImpl(CancellationToken t, DeviceRenderThreadBase renderThread)
        {
            //do things
            Tracer.TraceLine("CL Render Thread {0} started", renderThread.threadIndex);

#if VERBOSE
            try {
#endif
            var todoBuffers = new Queue<RayBuffer>();
            for (var i = 0; i < DeviceRenderBufferCount; i++)
                todoBuffers.Enqueue(renderThread.rayBuffers[i]);

            while (!t.IsCancellationRequested)
            {
                while (todoBuffers.Count > 0)
                {
                    var rayBuffer = todoBuffers.Dequeue();
                    rayBuffer.Reset();
                    renderThread._pathRayProcessors[0].FillRayBuffer(rayBuffer);
                    renderThread.intersectionDevice.PushRayBuffer(rayBuffer, renderThread.threadIndex);
                }
                var rayBuff = renderThread.intersectionDevice.PopRayBuffer(renderThread.threadIndex);
                if (rayBuff != null && rayBuff.UserData == renderThread.threadIndex)
                {
                    renderThread._pathRayProcessors[0].AdvancePaths(rayBuff);
                    todoBuffers.Enqueue(rayBuff);
                }
            }
#if VERBOSE
            }
            catch (Exception ex) {
                Tracer.TraceLine("Exiting rendering : Reason \n\r{0}", ex);
            }
#endif
            Tracer.TraceLine("CL Render Thread {0} exited", renderThread.threadIndex);
        }
    }
}
